/*
 * IMP alloc manager.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Aeolus <weiqing.yan@ingenic.com>
 */

#include <stdio.h>
#include <string.h>

#include <imp/imp_log.h>
#include <pthread.h>

#include "alloc_manager.h"
#include "buddy_alloc.h"
#include "continuous_alloc.h"
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
#define TAG "Alloc Manager"

/* alloc memory method */
extern int alloc_pmem_init(Alloc *alloc);
extern uint32_t alloc_pmem_get_paddr(uint32_t vaddr);
extern uint32_t alloc_pmem_get_vaddr(uint32_t paddr);

extern int alloc_kmem_init(Alloc *alloc);
extern uint32_t alloc_kmem_get_paddr(uint32_t vaddr);
extern uint32_t alloc_kmem_get_vaddr(uint32_t paddr);
extern int alloc_kmem_flush_cache(void *vaddr, size_t size, uint32_t dir);

extern int alloc_malloc_init(Alloc *alloc);
extern uint32_t alloc_malloc_get_paddr(uint32_t vaddr);

/* alloc manager interface */
static int alloc_add_info(AllocInfo *head, AllocInfo *info)
{
	AllocInfo *tmp = head;

	while(tmp->n_info != NULL)
		tmp = tmp->n_info;

	tmp->n_info = info;
	return 0;
}

static int alloc_del_info(AllocInfo *head, uint32_t addr)
{
	AllocInfo *tmp_p = head;
	AllocInfo *tmp_n = head;

	if(head->n_info != NULL) {
		tmp_n = tmp_p->n_info;
	}

	while(1) {
		if(tmp_n->vaddr == addr) {
			tmp_p->n_info = tmp_n->n_info;
			free(tmp_n);
			break;
		}

		if(tmp_n->n_info != NULL) {
			tmp_p = tmp_p->n_info;
			tmp_n = tmp_n->n_info;
		} else {
			break;
		}
	}
	return 0;
}

static AllocInfo *alloc_get_info(Alloc *alloc, void *addr)
{
	AllocInfo *tmp = &alloc->info;

	while(tmp != NULL) {
		if(tmp->vaddr == (uint32_t)addr)
			break;
		tmp = tmp->n_info;
	}

	return tmp;
}

static int alloc_dump_info(AllocInfo *head)
{
	AllocInfo *tmp = head;

	while(tmp != NULL) {
		IMP_LOG_INFO(TAG, "\ninfo->n_info = %p\n", tmp->n_info);
		IMP_LOG_INFO(TAG, "info->owner = %s\n", tmp->owner);
		IMP_LOG_INFO(TAG, "info->vaddr = 0x%08x\n", tmp->vaddr);
		IMP_LOG_INFO(TAG, "info->paddr = 0x%08x\n", tmp->paddr);
		IMP_LOG_INFO(TAG, "info->length = %d\n", tmp->length);
		IMP_LOG_INFO(TAG, "info->ref_cnt = %d\n", tmp->ref_cnt);
		IMP_LOG_INFO(TAG, "info->mem_attr = %d\n", tmp->mem_attr);
		tmp = tmp->n_info;
	}

	return 0;
}

static int alloc_dump_to_file(AllocInfo *head)
{
	AllocInfo *tmp = head;
	FILE *fd = fopen("/tmp/alloc_manager_info", "w");
	if(fd == NULL) {
		IMP_LOG_ERR(TAG, "Open /tmp/alloc_manager_info failed\n");
		return -1;
	}

	while(tmp != NULL) {
		char buf[512] = "";
		int len = 0;
		len = sprintf(buf, "\ninfo->n_info = %p\n", tmp->n_info);
		len += sprintf(buf+len, "info->owner = %s\n", tmp->owner);
		len += sprintf(buf+len, "info->vaddr = 0x%08x\n", tmp->vaddr);
		len += sprintf(buf+len, "info->paddr = 0x%08x\n", tmp->paddr);
		len += sprintf(buf+len, "info->length = %d\n", tmp->length);
		len += sprintf(buf+len, "info->ref_cnt = %d\n", tmp->ref_cnt);
		len += sprintf(buf+len, "info->mem_attr = %d\n", tmp->mem_attr);
		tmp = tmp->n_info;

		fwrite(buf, len, 1, fd);
	}

	fclose(fd);

	return 0;
}


int allocInit(Alloc *alloc)
{
	int ret = 0;
	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return -1;
	}
	pthread_mutex_init(&alloc->mutex, NULL);

	if(alloc->init_status == ALLOC_ALREADY_INITIALIZED)
		return 0;

	// alloc function
	alloc->alloc_mem = allocMem;
	alloc->sp_alloc_mem = spAllocMem;
	alloc->free_mem = freeMem;
	alloc->getAllocInfo = alloc_get_info;
	alloc->get_mem_attr = getMemAttr;
	alloc->set_mem_attr = setMemAttr;
	alloc->dump_mem_status = dumpMemStatus;
	alloc->dump_mem_to_file = dumpMemToFile;

	// MEM Alloc
	IMP_LOG_INFO(TAG, "MEM Alloc Method is %s\n", alloc->mem_alloc.method);
	if(!strcmp(alloc->mem_alloc.method, "pmem")) {
		alloc->mem_alloc.init = alloc_pmem_init;
		alloc->mem_alloc.get_paddr = alloc_pmem_get_paddr;
		alloc->mem_alloc.get_vaddr = alloc_pmem_get_vaddr;
	} else if(!strcmp(alloc->mem_alloc.method, "kmalloc")) {
		alloc->mem_alloc.init = alloc_kmem_init;
		alloc->mem_alloc.get_paddr = alloc_kmem_get_paddr;
		alloc->mem_alloc.get_vaddr = alloc_kmem_get_vaddr;
		alloc->mem_alloc.flush_cache = alloc_kmem_flush_cache;
	} else if(!strcmp(alloc->mem_alloc.method, "malloc")) {
		alloc->mem_alloc.init = alloc_malloc_init;
		alloc->mem_alloc.get_paddr = alloc_malloc_get_paddr;
	} else {
		alloc->mem_alloc.init = alloc_pmem_init; /* default method is pmem */
		alloc->mem_alloc.get_paddr = alloc_pmem_get_paddr;
		alloc->mem_alloc.get_vaddr = alloc_pmem_get_vaddr;
	}

	if(alloc->mem_alloc.init) {
		if(alloc->mem_alloc.init(alloc)) {
			IMP_LOG_ERR(TAG, "%s function Mem Alloc init error\n", __func__);
			return -1;
		}
	}

	// MEM Manager
	IMP_LOG_INFO(TAG, "MEM Manager Method is %s\n", alloc->mem_manager.method);
	if(!strcmp(alloc->mem_manager.method, "buddy")) {
		alloc->mem_manager.init = buddy_init;
		alloc->mem_manager.memalloc = buddy_alloc;
		alloc->mem_manager.memfree = buddy_free;
		alloc->mem_manager.dump = buddy_dump;
	} else if(!strcmp(alloc->mem_manager.method, "continuous")) {
		alloc->mem_manager.init = continuous_init;
		alloc->mem_manager.memalloc = continuous_alloc;
		alloc->mem_manager.memfree = continuous_free;
		alloc->mem_manager.dump = continuous_dump;
		alloc->mem_manager.dump_to_file = continuous_dump_to_file;
	} else {
		alloc->mem_manager.init = buddy_init; /* default method is buddy */
		alloc->mem_manager.memalloc = buddy_alloc;
		alloc->mem_manager.memfree = buddy_free;
		alloc->mem_manager.dump = buddy_dump;
	}

	if(alloc->mem_manager.init) {
		ret = alloc->mem_manager.init((void *)alloc->mem_alloc.vaddr,
				alloc->mem_alloc.length);
		if(ret != 0) {
			IMP_LOG_ERR(TAG, "%s function Mem Manager init error\n", __func__);
			return -1;
		}
	}

	alloc->init_status = ALLOC_ALREADY_INITIALIZED;

	return 0;
}

void *allocMem(Alloc *alloc, int size, char *owner)
{
	void *addr = NULL;
	AllocInfo *info = NULL;

	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return NULL;
	}

	pthread_mutex_lock(&(alloc->mutex));
	if(alloc->mem_manager.memalloc) {
		addr = alloc->mem_manager.memalloc(size);
		if(addr == NULL) {
			IMP_LOG_ERR(TAG, "%s mem_manager->alloc is failed\n", __func__);
            pthread_mutex_unlock(&(alloc->mutex));
            return NULL;
        }
    } else {
        IMP_LOG_ERR(TAG, "%s mem_manager->alloc is NULL\n", __func__);
        pthread_mutex_unlock(&(alloc->mutex));
        return NULL;
    }

    info = (AllocInfo *)calloc(1, sizeof(AllocInfo));
    if(info == NULL) {
        IMP_LOG_ERR(TAG, "Alloc Info struct malloc failed\n");
        pthread_mutex_unlock(&(alloc->mutex));
        return NULL;
    }

    info->n_info = NULL;
    strncpy(info->owner, owner, sizeof(info->owner) - 1);
    info->vaddr = (uint32_t)addr;
    info->length = size;
    info->ref_cnt = 1;

    if(alloc->mem_alloc.get_paddr) {
        info->paddr = alloc->mem_alloc.get_paddr(info->vaddr);
    } else {
        IMP_LOG_ERR(TAG, "%s mem_alloc->get_paddr is NULL\n", __func__);
        free(info);
        pthread_mutex_unlock(&(alloc->mutex));
        return NULL;
    }

    if(alloc_add_info(&alloc->info, info) != 0) {
        IMP_LOG_ERR(TAG, "%s alloc add info failed\n", __func__);
        free(info);
        pthread_mutex_unlock(&(alloc->mutex));
        return NULL;
    }

    pthread_mutex_unlock(&(alloc->mutex));
	return addr;
}

void *spAllocMem(Alloc *alloc, void *ptr)
{
	AllocInfo *info = NULL;

	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return NULL;
	}

	info = alloc_get_info(alloc, ptr);
	if(info == NULL) {
		IMP_LOG_ERR(TAG, "%s alloc get info failed\n", __func__);
		return NULL;
	}

	info->ref_cnt += 1;

	return ptr;
}

void freeMem(Alloc *alloc, void *ptr)
{
	AllocInfo *info = NULL;

	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return;
	}

    pthread_mutex_lock(&(alloc->mutex));
    info = alloc_get_info(alloc, ptr);
    if(info == NULL) {
        IMP_LOG_ERR(TAG, "%s alloc get info failed\n", __func__);
        pthread_mutex_unlock(&(alloc->mutex));
        return;
    }

    info->ref_cnt -= 1;
    if(info->ref_cnt == 0) {
        if(alloc->mem_manager.memfree) {
            alloc->mem_manager.memfree(ptr);
        } else {
            IMP_LOG_ERR(TAG, "alloc->mem_manager->free is NULL\n");
            pthread_mutex_unlock(&(alloc->mutex));
            return;
        }
        if(alloc_del_info(&alloc->info, (uint32_t)ptr)) {
            IMP_LOG_ERR(TAG, "alloc del info failed\n");
            pthread_mutex_unlock(&(alloc->mutex));
            return;
        }
    }

    pthread_mutex_unlock(&(alloc->mutex));
	return;
}

IMPMemAttr getMemAttr(Alloc *alloc, void *ptr)
{
	AllocInfo *info = NULL;

	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return -1;
	}

	info = alloc_get_info(alloc, ptr);
	if(info == NULL) {
		IMP_LOG_ERR(TAG, "%s alloc get info failed\n", __func__);
		return -1;
	}

	return info->mem_attr;
}

int setMemAttr(Alloc *alloc, void *ptr, IMPMemAttr attr)
{
	AllocInfo *info = NULL;

	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return -1;
	}

	info = alloc_get_info(alloc, ptr);
	if(info == NULL) {
		IMP_LOG_ERR(TAG, "%s alloc get info failed\n", __func__);
		return -1;
	}

	info->mem_attr = attr;

	return 0;
}

int dumpMemStatus(Alloc *alloc)
{
	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return -1;
	}

	if(alloc->mem_manager.dump) {
		alloc->mem_manager.dump();
	}

	alloc_dump_info(&alloc->info);
	return 0;
}

int dumpMemToFile(Alloc *alloc)
{
	if(alloc == NULL) {
		IMP_LOG_ERR(TAG, "%s function param is NULL\n", __func__);
		return -1;
	}

	if(alloc->mem_manager.dump_to_file) {
		alloc->mem_manager.dump_to_file();
	}

	alloc_dump_to_file(&alloc->info);
	return 0;
}
